package com.fusiondox {

	/*
		Copyright 2009 Andrew C Schwabe

		Licensed under the Apache License, Version 2.0 (the "License"); 
		you may not use this file except in compliance with the License. 
		You may obtain a copy of the License at 
		
			http://www.apache.org/licenses/LICENSE-2.0 
			
		Unless required by applicable law or agreed to in writing, software 
		distributed under the License is distributed on an "AS IS" BASIS, 
		WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
		See the License for the specific language governing permissions and 
		limitations under the License. 

		App:		Codex framework
		Version:	0.41
		Author:		Andrew Schwabe <aschwabe@iexp.com>
		
		Premise:
			Simple storage, nothing else.
			
			Put things in the Codex that can (and should) be reused.
			
			Examples: array collections that are used or referenced from multiple components,
					  global variables,
					  objects that are expensive to instantiate

		Modifications:

			01/18/2009 by ACS - all overwriting when adding objects; 
								added an objectExists() method


		Reference:
			http://makeagame.wordpress.com/2007/05/26/singleton-in-actionscript-30/
			http://www.returnundefined.com/2008/03/creating-bindable-calculated-read-only-properties-in-flex
			http://www.reflektions.com/miniml/template_permalink.asp?id=438			

		Updates:
			17-Dec-2008 by ACS - fix bug with "delete object" method and changed return type to void
			17 Aug 2009 by ACS - added codex bindings so when content objects are updated, Codex injects values to your components
			18-Aug 2009 by ACS - added boolean flag to addBinding() so when you setup the binding, you can request the first push of data

	*/

	import mx.collections.ArrayCollection;
	import mx.controls.Alert;
	import mx.messaging.ChannelSet;
	import mx.messaging.channels.AMFChannel;
	import mx.messaging.channels.SecureAMFChannel;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.remoting.RemoteObject;

    public class Codex 
    {
	    private static var privateCall:Boolean;
	    private static var instance:Codex;
	    private static var acObjects:ArrayCollection;
	    private static var acBindings:ArrayCollection;
		private static var frameworkVersion:String = "0,4,1,0";
	
	    public function Codex () 
		{
	    	if (!privateCall)
	    		throw new Error ("This is a Singleton Class");

			// initialize the object store
	    	acObjects = new ArrayCollection();
	    	acBindings = new ArrayCollection();
	 	}
	    
		public static function getInstance():Codex 
		{
	    	if (instance == null) 
	    	{
	    		privateCall= true;
	    		Codex.instance = new Codex();
	    		privateCall= false;
	    	}
	    	return Codex.instance;
	    }

		/* functions to set and retrieve objects */
		public function addObject(label:String, obj:Object, hint:String = "Object",allowOverwrite:Boolean = false):void 
		{
	    	if (instance == null) 
	    	{
	    		throw new Error("Instance not initialized");
	    	}

			var bSendUpdateEvents:Boolean = false;

			// check if obj already exists...
	    	for (var k:Number=0; k<acObjects.length; k++)
	    	{
	    		if (label == acObjects.getItemAt(k).label)
	    		{
	    			if (allowOverwrite == false)
					{
			    		throw new Error("Object would be overwritten");
					}
					else
					{
						acObjects.removeItemAt(k);
						bSendUpdateEvents = true;
					}
		    	}
	    	}

			// store new object
	    	var newObj:Object = new Object();
	    	newObj.label = label;
	    	newObj.data = obj;
	    	newObj.hint = hint;
	    	acObjects.addItem(newObj);
	    	
			/* inject values where requested */
	    	if (bSendUpdateEvents)
	    	{
		    	for (k=0; k<acBindings.length; k++)
		    	{
		    		if (label == acBindings.getItemAt(k).label)
		    		{
		    			var myContainer:Object = acBindings.getItemAt(k).container as Object;
		    			if (myContainer.hasOwnProperty(acBindings.getItemAt(k).target))
		    			{
		    				myContainer[acBindings.getItemAt(k).target] = obj;
		    				//Alert.show(myContainer.hasOwnProperty("myData").toString());
		    			}
		    			else
		    			{
		    				Alert.show("Codex binding target is not accessible, or is private");
		    			}
		    		}
		    	}
	    	}
	    }

		[Bindable(event="propertyChange")]
	    public function getObject(label:String):Object
	    {
	    	if (instance == null) 
	    	{
	    		throw new Error("Instance not initialized");
	    	}
	    	for (var k:Number=0; k<acObjects.length; k++)
	    	{
	    		if (label == acObjects.getItemAt(k).label)
	    			return acObjects.getItemAt(k).data;
	    	}
	    	throw new Error("Object with label: "+label+" not found in Codex");
	    }

	    public function getObjectHint(label:String):String
	    {
	    	if (instance == null) 
	    	{
	    		throw new Error("Instance not initialized");
	    	}
	    	for (var k:Number=0; k<acObjects.length; k++)
	    	{
	    		if (label == acObjects.getItemAt(k).label)
	    			return acObjects.getItemAt(k).hint;
	    	}
	    	throw new Error("Object with label: "+label+" not found in Codex");
	    }

	    public function deleteObject(label:String):void
	    {
	    	if (instance == null) 
	    	{
	    		throw new Error("Instance not initialized");
	    	}
	    	for (var k:Number=0; k<acObjects.length; k++)
	    	{
	    		if (label == acObjects.getItemAt(k).label)
	    		{
	    			acObjects.removeItemAt(k);
	    			return;
	    		}
	    	}
	    	throw new Error("Object with label: "+label+" not found in Codex");
	    }

		public function objectExists(lookingFor:String):Boolean
		{
	    	if (instance == null) 
	    	{
	    		throw new Error("Instance not initialized");
	    	}

	    	for (var i:Number=0; i<acObjects.length; i++)
	    	{
	    		if (acObjects.getItemAt(i).label == lookingFor)
	    			return true;
	    	}
			return false;
		}

	    public function getObjectDictionary():ArrayCollection
	    {
	    	if (instance == null) 
	    	{
	    		throw new Error("Instance not initialized");
	    	}

	    	var newDictionary:ArrayCollection = new ArrayCollection();
	    	for (var i:Number=0; i<acObjects.length; i++)
	    	{
	    		var newObj:Object = new Object();
	    		newObj.label = acObjects.getItemAt(i).label;
	    		newObj.type = acObjects.getItemAt(i).hint;
	    		newDictionary.addItem(newObj);
	    	}
	    	return newDictionary;
	    }

		// add binding to an object in the Codex
	    public function addBinding(objLabel:String, objContainer:Object=null, objTarget:String=null, updateNow:Boolean=false):void
	    {
	    	if (instance == null) 
	    	{
	    		throw new Error("Instance not initialized");
	    	}
	    	if (objTarget == null)
	    	{
	    		Alert.show("target object is null");
	    		return;
	    	}

	    	// add into to acBindings
	    	var newObj:Object = new Object();
	    	newObj.label = objLabel;
	    	newObj.container = objContainer;
	    	newObj.target = objTarget;
	    	acBindings.addItem(newObj);

			if (updateNow)
			{
    			if (objContainer.hasOwnProperty(objTarget))
    			{
    				objContainer[objTarget] = getObject(objLabel);
    			}
    			else
    			{
    				Alert.show("Codex binding target is not accessible, or is private");
    			}
			}

	    }

	    public function removeBinding(objLabel:String, objContainer:Object, objInject:String):void
	    {
	    	if (instance == null) 
	    	{
	    		throw new Error("Instance not initialized");
	    	}
	    	for (var k:Number=0; k<acBindings.length; k++)
	    	{
	    		if ((objLabel == acBindings.getItemAt(k).label) && 
	    		    (objInject == acBindings.getItemAt(k).target) &&
	    		    (objContainer == acBindings.getItemAt(k).container))
	    		{
	    			acBindings.removeItemAt(k);
	    			return;
	    		}
	    	}
	    	throw new Error("Binding with label: "+objLabel+" not found in Codex");
	    }

	    public function removeBindings(objLabel:String):void
	    {
	    	if (instance == null) 
	    	{
	    		throw new Error("Instance not initialized");
	    	}
	    	for (var k:Number=0; k<acBindings.length; k++)
	    	{
	    		if (objLabel == acBindings.getItemAt(k).label)
	    		{
	    			acBindings.removeItemAt(k);
	    		}
	    	}
	    }
		// get the binding list 
	    public function getBindings():ArrayCollection
	    {
	    	if (instance == null) 
	    	{
	    		throw new Error("Instance not initialized");
	    	}

	    	// add into to acBindings
	    	return acBindings;
	    }


		/* default handlers */
		public function defaultFaultHandler(event:FaultEvent):void
		{
			Alert.show("An error occurred retrieving data from the remote service:\n" + event.fault);
		}


		/* utility classes */
		public function configRemoteObject(destName:String, channelName:String, gatewayURI:String, cfc:String, useSsl:Boolean):RemoteObject 
		{
			var cs:ChannelSet = new ChannelSet();
			if (useSsl) 
				cs.addChannel(new SecureAMFChannel(channelName, gatewayURI));
			else 
				cs.addChannel(new AMFChannel(channelName, gatewayURI));

			var ro:RemoteObject = new RemoteObject();
			ro.destination = destName;
			ro.channelSet = cs;
			ro.source = cfc;

			return ro;
		}

	}
}